Thinking in React
Start with the mockup
以下のデータをJSON APIから取得したと仮定する。
code:json
[
{ category: "Fruits", price: "$1", stocked: true, name: "Apple" },
{ category: "Fruits", price: "$1", stocked: true, name: "Dragonfruit" },
{ category: "Fruits", price: "$2", stocked: false, name: "Passionfruit" },
{ category: "Vegetables", price: "$2", stocked: true, name: "Spinach" },
{ category: "Vegetables", price: "$4", stocked: false, name: "Pumpkin" },
{ category: "Vegetables", price: "$1", stocked: true, name: "Peas" }
]
画面のモックアップはこんな感じ。
https://gyazo.com/4bebd8966abde04e167e5b1ba42b4da0
Reactでは以下の5つのステップでUIを実装していく。
Step 1: Break the UI into a component hierarchy
Step 2: Build a static version in React
Step 3: Find the minimal but complete representation of UI state
Step 4: Identify where your state should live
Step 5: Add inverse data flow
Step 1: Break the UI into a component hierarchy
https://gyazo.com/45687b56c4dfece73abc973acd88b5c8
FilterableProductTable
SearchBar
ProductTable
ProductCategoryTable
ProductRow
Step 2: Build a static version in React
code:src/App.js
function ProductRow({product}) {
return (
<tr>
<td
style={{
color: product.stocked ? 'black' : 'red'
}}
{product.name}
</td>
<td>{product.price}</td>
</tr>
);
}
function ProductCategoryRow({name}) {
return (
<tr><th colSpan="2">{name}</th></tr>
);
}
function ProductTable({products}) {
const rows = [];
const fruits = products.filter((e) => e.category === "Fruits");
rows.push(<ProductCategoryRow key="Fruits" name="Fruits" />);
fruits.forEach((e) => {
rows.push(
<ProductRow key={e.name} product={e} />
);
});
const vegetables = products.filter((e) => e.category === "Vegetables");
rows.push(<ProductCategoryRow key="Vegetables" name="Vegetables" />);
vegetables.forEach((e) => {
rows.push(
<ProductRow key={e.name} product={e} />
);
});
return (
<>
<table>
<thead>
<tr key="head">
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
</>
);
}
function SearchBar() {
return (
<form>
<input type="text" />
<label>
<input type="checkbox" />
{' '}
Only show products in stock
</label>
</form>
);
}
const products = [
{ category: "Fruits", price: "$1", stocked: true, name: "Apple" },
{ category: "Fruits", price: "$1", stocked: true, name: "Dragonfruit" },
{ category: "Fruits", price: "$2", stocked: false, name: "Passionfruit" },
{ category: "Vegetables", price: "$2", stocked: true, name: "Spinach" },
{ category: "Vegetables", price: "$4", stocked: false, name: "Pumpkin" },
{ category: "Vegetables", price: "$1", stocked: true, name: "Peas" }
];
export default function FilterableProductTable() {
return (
<>
<SearchBar />
<ProductTable products={products} />
</>
);
}
Step 3: Find the minimal but complete representation of UI state
Step 4: Identify where your state should live
Step 5: Add inverse data flow
code:src/App.js
import { useState } from 'react';
function ProductRow({product}) {
return (
<tr>
<td
style={{
color: product.stocked ? 'black' : 'red'
}}
{product.name}
</td>
<td>{product.price}</td>
</tr>
);
}
function ProductCategoryRow({name}) {
return (
<tr><th colSpan="2">{name}</th></tr>
);
}
function ProductTable({products, filterText, inStockOnly}) {
const rows = [];
let filteredProducts = products;
if (filterText) {
let regexp =
console.log(filterText);
filteredProducts = products.filter((e) => RegExp(filterText, "i").test(e.name));
}
if (inStockOnly) {
filteredProducts = filteredProducts.filter((e) => e.stocked);
}
const fruits = filteredProducts.filter((e) => {
return e.category === "Fruits" && (!inStockOnly || e.stocked)
});
rows.push(<ProductCategoryRow key="Fruits" name="Fruits" />);
fruits.forEach((e) => {
rows.push(
<ProductRow key={e.name} product={e} />
);
});
const vegetables = filteredProducts.filter((e) => e.category === "Vegetables");
rows.push(<ProductCategoryRow key="Vegetables" name="Vegetables" />);
vegetables.forEach((e) => {
rows.push(
<ProductRow key={e.name} product={e} />
);
});
return (
<>
<table>
<thead>
<tr key="head">
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
</>
);
}
function SearchBar({filterText, inStockOnly, onChange, onCheck}) {
return (
<form>
<input type="text" value={filterText} onChange={onChange}/>
<label>
<input type="checkbox" checked={inStockOnly} onChange={onCheck}/>
{' '}
Only show products in stock
</label>
</form>
);
}
const products = [
{ category: "Fruits", price: "$1", stocked: true, name: "Apple" },
{ category: "Fruits", price: "$1", stocked: true, name: "Dragonfruit" },
{ category: "Fruits", price: "$2", stocked: false, name: "Passionfruit" },
{ category: "Vegetables", price: "$2", stocked: true, name: "Spinach" },
{ category: "Vegetables", price: "$4", stocked: false, name: "Pumpkin" },
{ category: "Vegetables", price: "$1", stocked: true, name: "Peas" }
];
export default function FilterableProductTable() {
function handleTextChange(e) {
setFilterText(e.target.value);
}
function handleCheck() {
setInStockOnly(!inStockOnly);
}
return (
<>
<SearchBar filterText={filterText} inStockOnly={inStockOnly} onChange={handleTextChange} onCheck={handleCheck} />
<ProductTable
products={products}
filterText={filterText}
inStockOnly={inStockOnly}
/>
</>
);
}